/*
 * Copyright (c) 2015, Ford Motor Company
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following
 * disclaimer in the documentation and/or other materials provided with the
 * distribution.
 *
 * Neither the name of the Ford Motor Company nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <errno.h>
#include <limits.h>
#include <stddef.h>
#include <signal.h>
#include <pthread.h>
#include <algorithm>
#include <functional>

#include "threads/thread.h"
#include "atomic.h"
#include "threads/thread_delegate.h"
#include "logger.h"

#ifndef __QNXNTO__
const int EOK = 0;
#endif

#if defined(OS_POSIX)
const size_t THREAD_NAME_SIZE = 128;
#endif

namespace threads {

size_t Thread::kMinStackSize =
    PTHREAD_STACK_MIN; /* Ubuntu : 16384 ; QNX : 256; */

void Thread::cleanup(void* arg) {
  Thread* thread = reinterpret_cast<Thread*>(arg);
  AutoLock auto_lock(thread->state_lock_);
  thread->isThreadRunning_ = false;
  thread->thread_created_ = false;
  thread->state_cond_.Broadcast();
}

void* Thread::threadFunc(void* arg) {
  // 0 - state_lock unlocked
  //     stopped   = 0
  //     running   = 0
  //     finalized = 0
  // 4 - state_lock unlocked
  //     stopped = 1
  //     running = 1
  //     finalized = 0
  // 5 - state_lock unlocked
  //     stopped = 1
  //     running = 1
  //     finalized = 1
  pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

  threads::Thread* thread = reinterpret_cast<Thread*>(arg);
  DCHECK(thread);

  pthread_cleanup_push(&cleanup, thread);

  thread->state_lock_.Acquire();
  thread->state_cond_.Broadcast();

//  while (!thread->finalized_) {
    thread->run_cond_.Wait(thread->state_lock_);
    if (!thread->stopped_ && !thread->finalized_) {
      thread->isThreadRunning_ = true;
      thread->state_lock_.Release();

      pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
      pthread_testcancel();
      if (!thread->delegate_)
          exit(EXIT_FAILURE);
      thread->delegate_->threadMain();
      pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

      thread->state_lock_.Acquire();
      thread->isThreadRunning_ = false;
    }
//    thread->state_cond_.Broadcast();
//  }

  thread->state_lock_.Release();
  pthread_cleanup_pop(1);
  return NULL;
}

void Thread::SetNameForId(const PlatformThreadHandle& thread_id,
                          std::string name) {
#ifndef HTNICE_OLDK4
  if (name.size() > THREAD_NAME_SIZE)
    name.erase(THREAD_NAME_SIZE);
  const int rc = pthread_setname_np(thread_id, name.c_str());
  if (rc != EOK) {
    LOG_WARN("Couldn't set pthread name \" %s \" ,error code %d(%s)",
             name.c_str(),
             rc,strerror(rc));
  }
#endif
}

Thread::Thread(const char* name, ThreadDelegate* delegate)
    : name_(name ? name : "undefined")
    , delegate_(delegate)
    , handle_(0)
    , thread_options_(PTHREAD_STACK_MIN*2,false)
    , isThreadRunning_(0)
    , stopped_(false)
    , finalized_(false)
    , thread_created_(false)
    , state_lock_(true){}

bool Thread::start() {
  return start(thread_options_);
}

PlatformThreadHandle Thread::CurrentId() {
  return pthread_self();
}

bool Thread::IsCurrentThread() const {
  return pthread_equal(CurrentId(), thread_handle());
}

bool Thread::start(const ThreadOptions& options) {

  AutoLock auto_lock(state_lock_);
  // 1 - state_lock locked
  //     stopped = 0
  //     running = 0

  if (!delegate_) {
    LOG_ERR("Cannot start thread %s",name_.c_str());
    // 0 - state_lock unlocked
    return false;
  }

  if (isThreadRunning_) {
    LOG_ERR("EXIT thread %s,is already running",name_.c_str());
    return true;
  }

  thread_options_ = options;

  pthread_attr_t attributes;
  int pthread_result = pthread_attr_init(&attributes);
  if (pthread_result != EOK) {
    LOG_WARN("Couldn't init pthread attributes. Error code = %d(%s)"
                     ,pthread_result,strerror(pthread_result));
  }

  if (!thread_options_.is_joinable()) {
    pthread_result =
        pthread_attr_setdetachstate(&attributes, PTHREAD_CREATE_DETACHED);
    if (pthread_result != EOK) {
      LOG_WARN("Couldn't set detach state attribute. Error code = %d(%s)"
                       ,pthread_result,strerror(pthread_result));
      thread_options_.is_joinable(false);
    }
  }

  const size_t stack_size = thread_options_.stack_size();
  if (stack_size >= Thread::kMinStackSize) {
    pthread_result = pthread_attr_setstacksize(&attributes, stack_size);
    if (pthread_result != EOK) {
      LOG_WARN("Couldn't set stacksize = %d,Error code = %d(%s)"
                       ,stack_size,pthread_result,strerror(pthread_result));
    }
  } else {
    ThreadOptions thread_options_temp(Thread::kMinStackSize,
                                      thread_options_.is_joinable());
    thread_options_ = thread_options_temp;
  }

  if (!thread_created_) {
    // state_lock 1
    pthread_result = pthread_create(&handle_, &attributes, threadFunc, this);
    if (pthread_result == EOK) {
      SetNameForId(handle_, name_);
      // state_lock 0
      // possible concurrencies: stop and threadFunc
      state_cond_.Wait(auto_lock);
      thread_created_ = true;
    } else {
      LOG_ERR("Couldn't create thread %s,Error code = %d(%s)"
                        ,name_.c_str(),pthread_result,strerror(pthread_result));
    }
  }
  stopped_ = false;
  run_cond_.NotifyOne();
  pthread_attr_destroy(&attributes);
  return pthread_result == EOK;
}

void Thread::yield() {
  sched_yield();
}

void Thread::stop() {
  AutoLock auto_lock(state_lock_);

  stopped_ = true;

  if (delegate_ && isThreadRunning_) {
    delegate_->exitThreadMain();
  }
}

void Thread::join() {
//  DCHECK_OR_RETURN_VOID(!IsCurrentThread());

  stop();

  AutoLock auto_lock(state_lock_);
  run_cond_.NotifyOne();
  if (isThreadRunning_) {
    if (!pthread_equal(pthread_self(), handle_)) {
#ifndef HTNICE_OLDK4
        char curr[128] = {0};
        pthread_getname_np(pthread_self(),curr,128);
        LOG_DEBUG("Waiting for # %s finished iteration in thread # %s",
                  name_.c_str(),
                  curr);
#else
        LOG_DEBUG("Waiting for # %s finished iteration in thread # %lu",
                  name_.c_str(),
                  pthread_self());
#endif
        state_cond_.Wait(auto_lock);
        pthread_join(handle_,NULL);
    }
  }
}

Thread::~Thread() {
    AutoLock auto_lock(state_lock_);
    finalized_ = true;
    stopped_ = true;
    join();
    // in some platforms pthread_join behaviour is undefined when thread is
    // not created(pthread_create) and call pthread_join.
}

Thread* CreateThread(const char* name, ThreadDelegate* delegate) {
  Thread* thread = new Thread(name, delegate);
  delegate->set_thread(thread);
  return thread;
}

void DeleteThread(Thread* thread) {
  delete thread;
}

}  // namespace threads
